`include "ALU.v"
`include "RegisterFile.v"
`include "ImmediateExtractor.v"
`include "Encoders.v"

module CPU (
	input clk,
	input [31:0] ram_read_data,
	input [31:0] instruction,

	output [9:0] ram_addr,
	output reg [31:0] ram_write_data,
	output ram_write_enable,
	output [9:0] instruction_addr
);
////////////////////////////////////////////////////////////////////////////////////////////////
//////   Define Parameter here                                                          ////////
////////////////////////////////////////////////////////////////////////////////////////////////
	parameter [6:0] op_R_type		= 7'h33;
	parameter [6:0] op_I_type_load	= 7'h03;
	parameter [6:0] op_I_type_other	= 7'h13;
	parameter [6:0] op_I_type_jump	= 7'h6F;
	parameter [6:0] op_S_type		= 7'h23;
	parameter [6:0] op_B_type		= 7'h63;
	parameter [6:0] op_U_type_load	= 7'h37;
	parameter [6:0] op_U_type_jump	= 7'h67;
	parameter [6:0] op_U_type_auipc	= 7'h17;

	parameter type_register 		= 0;
	parameter type_load 			= 1;
	parameter type_store 			= 2;
	parameter type_immediate 		= 3;
	parameter type_upperImmediate 	= 4;
	parameter type_branch 			= 5;

	parameter decode 			= 0;
	parameter execute 			= 1;
	parameter memory 			= 2;
	parameter writeback 		= 3;

////////////////////////////////////////////////////////////////////////////////////////////////
//////   Define Segment registers here                                                  ////////
////////////////////////////////////////////////////////////////////////////////////////////////
	reg [9:0] pc = 0;
	assign instruction_addr = pc >> 2;
	
	reg [9:0] pc_decode_2 = 0;
	reg [31:0] instruction_deconde_2 = 0;

	reg [9:0] pc_execute_3 = 0;
	reg [31:0] instruction_execute_3 = 0;

	reg [9:0] pc_memory_4 = 0;
	reg [31:0] instruction_memory_4 = 0;
	reg [31:0] alu_out_memory_4;

	reg [31:0] instruction_writeback_5 = 0;
	reg [31:0] reg_write_data_writeback_5 = 0;
	reg [31:0] ram_read_data_writeback_5 = 0;


////////////////////////////////////////////////////////////////////////////////////////////////
//////   Define Hazard Signal here                                                          ////////
////////////////////////////////////////////////////////////////////////////////////////////////
	wire data_dependence_hazard_r1;
	wire data_dependence_hazard_r2;
	wire data_dependence_hazard_r1_writeback;
	wire data_dependence_hazard_r2_writeback;
	wire load_stall;
	wire control_hazard_stall;

	wire [1:0] reg_writeback_selection;

	wire signed [31:0] reg_source_1_data_execute_3;
	wire signed [31:0] reg_source_2_data_execute_3;


////////////////////////////////////////////////////////////////////////////////////////////////
//////   Define ALU Signals here                                                        ////////
////////////////////////////////////////////////////////////////////////////////////////////////


	wire [6:0] opcode				= instruction_execute_3[6:0];
	wire [4:0] reg_destinate		= instruction_writeback_5[11:7];
	wire [2:0] funct3				= instruction_execute_3[14:12];
	wire [4:0] reg_source_1			= instruction_execute_3[19:15];
	wire [4:0] reg_source_2			= instruction_execute_3[24:20];
	wire [6:0] funct7				= instruction_execute_3[31:25];

	wire R_type 				= opcode == op_R_type;
	wire I_type_load 			= opcode == op_I_type_load;
	wire I_type_other  			= opcode == op_I_type_other;
	wire I_type_jump  			= opcode == op_I_type_jump;
	wire I_type 				= I_type_jump || I_type_other || I_type_load;
	wire S_type 				= opcode == op_S_type;
	wire B_type 				= opcode == op_B_type;
	wire U_type_load 			= opcode == op_U_type_load;
	wire U_type_jump 			= opcode == op_U_type_jump;
	wire U_type_auipc 			= opcode == op_U_type_auipc;
	wire U_type 				= U_type_load || U_type_jump || op_U_type_auipc;

	///////////////////////////////////////////////////////////////////////////
	//// Ten R-Type Instruction of RV32I                                   ////
	///////////////////////////////////////////////////////////////////////////
	wire R_add 				= R_type && funct3 == 3'h0 && funct7 == 7'h00;
	wire R_sub 				= R_type && funct3 == 3'h0 && funct7 == 7'h20;
	wire R_sll 				= R_type && funct3 == 3'h1 && funct7 == 7'h00;
	wire R_slt 				= R_type && funct3 == 3'h2 && funct7 == 7'h00;
	wire R_sltu 			= R_type && funct3 == 3'h3 && funct7 == 7'h00;
	wire R_xor 				= R_type && funct3 == 3'h4 && funct7 == 7'h00;
	wire R_srl 				= R_type && funct3 == 3'h5 && funct7 == 7'h00;
	wire R_sra 				= R_type && funct3 == 3'h5 && funct7 == 7'h20;
	wire R_or 				= R_type && funct3 == 3'h6 && funct7 == 7'h00;
	wire R_and 				= R_type && funct3 == 3'h7 && funct7 == 7'h00;


	///////////////////////////////////////////////////////////////////////////
	//// Thirteen I-Type Instruction of RV32I                              ////
	///////////////////////////////////////////////////////////////////////////
	wire I_addi 			= I_type_other && funct3 == 3'h0;
	wire I_slli 			= I_type_other && funct3 == 3'h1 && funct7 == 7'h00;
	wire I_slti 			= I_type_other && funct3 == 3'h2;
	wire I_sltiu 			= I_type_other && funct3 == 3'h3;
	wire I_xori 			= I_type_other && funct3 == 3'h4;
	wire I_srli 			= I_type_other && funct3 == 3'h5 && funct7 == 7'h00;
	wire I_srai 			= I_type_other && funct3 == 3'h5 && funct7 == 7'h10;
	wire I_ori 				= I_type_other && funct3 == 3'h6;
	wire I_andi 			= I_type_other && funct3 == 3'h7;

	wire I_lb 			= instruction_memory_4[6:0] == op_I_type_load && instruction_memory_4[14:12] == 3'h0;
	wire I_lh 			= instruction_memory_4[6:0] == op_I_type_load && instruction_memory_4[14:12] == 3'h1;
	wire I_lw 			= instruction_memory_4[6:0] == op_I_type_load && instruction_memory_4[14:12] == 3'h2;
	wire I_lbu			= instruction_memory_4[6:0] == op_I_type_load && instruction_memory_4[14:12] == 3'h4;
	wire I_lhu			= instruction_memory_4[6:0] == op_I_type_load && instruction_memory_4[14:12] == 3'h5;


	wire I_jalr			= I_type_jump;

	///////////////////////////////////////////////////////////////////////////
	//// Three U-Type Instruction of RV32I                                 ////
	///////////////////////////////////////////////////////////////////////////
	wire U_lui			= U_type_load;
	wire U_auipc 		= U_type_auipc;
	wire U_jal 			= U_type_jump;


	///////////////////////////////////////////////////////////////////////////
	//// Three S-Type Instruction of RV32I                                 ////
	///////////////////////////////////////////////////////////////////////////
	wire S_lb 			= instruction_memory_4[6:0] == op_S_type && instruction_memory_4[14:12] == 3'h0;
	wire S_lh 			= instruction_memory_4[6:0] == op_S_type && instruction_memory_4[14:12] == 3'h1;
	wire S_lw 			= instruction_memory_4[6:0] == op_S_type && instruction_memory_4[14:12] == 3'h2;

	///////////////////////////////////////////////////////////////////////////
	//// Six B-Type Instruction of RV32I                                   ////
	///////////////////////////////////////////////////////////////////////////
	wire B_beq 			= B_type && funct3 == 0;
	wire B_bne 			= B_type && funct3 == 1;
	wire B_blt 			= B_type && funct3 == 4;
	wire B_bge 			= B_type && funct3 == 5;
	wire B_bltu 		= B_type && funct3 == 6;
	wire B_bgeu 		= B_type && funct3 == 7;

	wire signed [31:0] reg_source_1_data = data_dependence_hazard_r1 ? alu_out_memory_4: 
											data_dependence_hazard_r1_writeback?
												(reg_writeback_selection == 3? ram_read_data_writeback_5: reg_write_data_writeback_5)
												: reg_source_1_data_execute_3;

	wire signed [31:0] reg_source_2_data = data_dependence_hazard_r2 ? alu_out_memory_4: 
											data_dependence_hazard_r2_writeback?
												(reg_writeback_selection == 3? ram_read_data_writeback_5: reg_write_data_writeback_5)
												: reg_source_2_data_execute_3;


	// wire [31:0] r1		= [:];

	wire [31:0] reg_source_1_data_unsigned = reg_source_1_data;
	wire [31:0] reg_source_2_data_unsigned = reg_source_2_data;

	// if processing branch type inst, and the branch result is true, set pc_alu_sel = 1
	wire pc_alu_sel = (B_beq && reg_source_1_data == reg_source_2_data)
						|| (B_bne && reg_source_1_data != reg_source_2_data)
						|| (B_blt && reg_source_1_data < reg_source_2_data)
						|| (B_bge && reg_source_1_data >= reg_source_2_data)
						|| (B_bltu && reg_source_1_data_unsigned < reg_source_2_data_unsigned)
						|| (B_bgeu && reg_source_1_data_unsigned >= reg_source_2_data_unsigned)
						|| I_jalr
						|| U_jal;

	assign ram_write_enable = instruction_memory_4[6:0] == op_S_type;
	assign ram_addr = alu_out_memory_4[9:0];


	reg [4:0] reg_source_1_pipeline[3:0];	// source register 1 's register of current stage, is a part of segment register
	reg [4:0] reg_source_2_pipeline[3:0];	// source register 2 's register of current stage, is a part of segment register
	reg [4:0] reg_destinate_pipeline[3:0]; 	// destinate register 's register of current stage, is a part of segment register
	reg [2:0] type_pipeline[3:0]; 			// instruction types of current stage. [R-Type=0, Load=1, Store=2, Immediate or UpperImmediate=3, Branch=4]

	assign data_dependence_hazard_r1 			= reg_source_1_pipeline[execute] != 0 
											&& type_pipeline[execute] != type_upperImmediate
											&& reg_source_1_pipeline[execute] == reg_destinate_pipeline[memory];

	assign data_dependence_hazard_r2 			= reg_source_2_pipeline[execute] != 0
											&& type_pipeline[execute] != type_upperImmediate
											&& type_pipeline[execute] != type_immediate
											&& reg_source_2_pipeline[execute] == reg_destinate_pipeline[memory];

	assign data_dependence_hazard_r1_writeback 			= reg_source_1_pipeline[execute] != 0
														&& type_pipeline[execute] != type_upperImmediate
														&& reg_source_1_pipeline[execute] == reg_destinate_pipeline[writeback];

	assign data_dependence_hazard_r2_writeback 			= reg_source_2_pipeline[execute] != 0
														&& type_pipeline[execute] != type_upperImmediate
														&& type_pipeline[execute] != type_immediate
														&& reg_source_2_pipeline[execute] == reg_destinate_pipeline[writeback];

	assign load_stall 						= type_pipeline[execute] == type_load
											&& (
													(
														type_pipeline[decode] != type_upperImmediate
														&& type_pipeline[decode] != type_immediate
														&& (
																(reg_source_1_pipeline[decode] != 0 && reg_source_1_pipeline[decode] == reg_destinate_pipeline[execute])
																|| (reg_source_2_pipeline[decode] != 0 && reg_source_2_pipeline[decode] == reg_destinate_pipeline[execute])
															)
													)
													|| (
															type_pipeline[decode] == type_immediate
															&& reg_source_1_pipeline[decode] != 0
															&& reg_source_1_pipeline[decode] == reg_destinate_pipeline[execute]
														)
												);

	assign control_hazard_stall 			= instruction_deconde_2[6:0] == op_B_type || instruction_execute_3[6:0] == op_B_type;

	// get the immediate value
	// ImmediateExtractor
	wire [31:0] immediate_value;
	wire [2:0] immediate_selection;
	wire [7:0] immediate_selection_inputs;

	assign immediate_selection_inputs[0] = 0;
	assign immediate_selection_inputs[1] = I_type;
	assign immediate_selection_inputs[2] = U_type_load || U_type_auipc;
	assign immediate_selection_inputs[3] = S_type;
	assign immediate_selection_inputs[4] = B_type;
	assign immediate_selection_inputs[5] = U_type_jump;
	assign immediate_selection_inputs[6] = 0;
	assign immediate_selection_inputs[7] = 0;

	Encoder_8 inst_Encoder_8 (
		.in(immediate_selection_inputs),
		.out(immediate_selection)
		);


	ImmediateExtractor inst_ImmediateExtractor (
		.instruction(instruction_execute_3), 
		.selection(immediate_selection), 
		.value(immediate_value)
		);

	// select the operation of alu
	wire [15:0] alu_operation_encoder_inputs;
	wire [3:0] alu_operation;

	assign alu_operation_encoder_inputs[0] = R_add || I_addi;
	assign alu_operation_encoder_inputs[1] = R_sub;
	assign alu_operation_encoder_inputs[2] = R_and || I_andi;
	assign alu_operation_encoder_inputs[3] = R_or || I_ori;
	assign alu_operation_encoder_inputs[4] = R_xor || I_xori;
	assign alu_operation_encoder_inputs[5] = R_sll || I_slli;
	assign alu_operation_encoder_inputs[6] = R_srl || I_srli;
	assign alu_operation_encoder_inputs[7] = R_sra || I_srai;
	assign alu_operation_encoder_inputs[8] = R_slt || I_slti;
	assign alu_operation_encoder_inputs[9] = R_sltu || I_sltiu;
	assign alu_operation_encoder_inputs[10] = 0;
	assign alu_operation_encoder_inputs[11] = 0;
	assign alu_operation_encoder_inputs[12] = 0;
	assign alu_operation_encoder_inputs[13] = 0;
	assign alu_operation_encoder_inputs[14] = 0;
	assign alu_operation_encoder_inputs[15] = 0;

	Encoder_16 inst_Encoder_16 (
		.in(alu_operation_encoder_inputs), 
		.out(alu_operation)
		);

	// select the input of alu
	wire [3:0] alu_source_1_selection_inputs;
	wire [3:0] alu_source_2_selection_inputs;
	wire [1:0] alu_source_1_selection;
	wire [1:0] alu_source_2_selection;

	assign alu_source_1_selection_inputs[0] = 1;
	assign alu_source_1_selection_inputs[1] = B_type || U_type_jump || U_type_auipc || I_type_jump;
	assign alu_source_1_selection_inputs[2] = U_type_load;
	assign alu_source_1_selection_inputs[3] = 0;


	assign alu_source_2_selection_inputs[0] = 1;
	assign alu_source_2_selection_inputs[1] = S_type || I_type || B_type || U_type;
	assign alu_source_2_selection_inputs[2] = 0;
	assign alu_source_2_selection_inputs[3] = 0;

	Encoder_4 inst_Encoder_4_1 (.in(alu_source_1_selection_inputs), .out(alu_source_1_selection));
	Encoder_4 inst_Encoder_4_2 (.in(alu_source_2_selection_inputs), .out(alu_source_2_selection));

	// implement alu
	reg [31:0] alu_source_1;
	reg [31:0] alu_source_2;
	wire [31:0] alu_out;
	wire is_equal;

	ALU inst_ALU(
			.reg_data_1 (alu_source_1),
			.reg_data_2 (alu_source_2),
			.opcode     (alu_operation),
			.alu_result (alu_out),
			.isEqual    (is_equal)
		);

	always @ (*) begin
		case (alu_source_1_selection) 
		    0: alu_source_1 = reg_source_1_data;
		    1: alu_source_1 = pc_execute_3;
		    default: alu_source_1 = 'b0;
		endcase
		case (alu_source_2_selection) 
		    0: alu_source_2 = reg_source_2_data;
		    1: alu_source_2 = immediate_value;
		    default: alu_source_2 = 'b0;
		endcase
	end

	// Register File

	wire [6:0] opcode_writeback_5		= instruction_writeback_5[6:0];
	wire writeback_R_type 				= opcode_writeback_5 == op_R_type;
	wire writeback_I_type_load 			= opcode_writeback_5 == op_I_type_load;
	wire writeback_I_type_other 		= opcode_writeback_5 == op_I_type_other;
	wire writeback_I_type_jump 			= opcode_writeback_5 == op_I_type_jump;
	wire writeback_I_type 				= writeback_I_type_load || writeback_I_type_other || writeback_I_type_jump;
	wire writeback_U_type_load 			= opcode_writeback_5 == op_U_type_load;
	wire writeback_U_type_jump 			= opcode_writeback_5 == op_U_type_jump;
	wire writeback_U_type_auipc 		= opcode_writeback_5 == op_U_type_auipc;
	wire writeback_U_type 				= writeback_U_type_jump || writeback_U_type_load || writeback_U_type_auipc;

	wire reg_write_enable 			= writeback_R_type || writeback_I_type || writeback_U_type;


	wire [3:0] reg_writeback_selection_inputs;
	

	assign reg_writeback_selection_inputs[0] = 0;
	assign reg_writeback_selection_inputs[1] = writeback_R_type || writeback_U_type_load || writeback_I_type_other;
	assign reg_writeback_selection_inputs[2] = writeback_U_type_jump || writeback_I_type_jump;
	assign reg_writeback_selection_inputs[3] = writeback_I_type_load;

	Encoder_4 writeback_selection_encoder(
		.in(reg_writeback_selection_inputs),
		.out(reg_writeback_selection)
		);

	// wire signed [31:0] reg_source_1_data_execute_3;
	// wire signed [31:0] reg_source_2_data_execute_3;

	wire [31:0] reg_writeback_data		= reg_writeback_selection == 3? ram_read_data_writeback_5: reg_write_data_writeback_5;

	RegisterFile inst_RegisterFile(
			.reg_source_1       (reg_source_1),
			.reg_source_2       (reg_source_2),
			.reg_destinate      (reg_destinate),
			.reg_destinate_data (reg_writeback_data),
			.write_enable       (reg_write_enable),
			.reg_source_1_data  (reg_source_1_data_execute_3),
			.reg_source_2_data  (reg_source_2_data_execute_3)
		);

	// pipelining
	// Stage 1: Fetch instruction, the instruction come from rom
	

	always @ (posedge clk) begin
		if (pc_alu_sel == 1) begin
			pc <= alu_out[9:0];
		end else begin
			if (load_stall == 1 || control_hazard_stall == 1) begin
				pc <= pc;
			end else begin
				pc <= pc + 4;
			end
		end

		if (control_hazard_stall == 1) begin
			reg_source_1_pipeline[decode]	<= 0;
			reg_source_2_pipeline[decode]	<= 0;
			reg_destinate_pipeline[decode]	<= 0;
			type_pipeline[decode]			<= type_immediate;
		end else begin
			reg_source_1_pipeline[decode] 	<= instruction[19:15];
			reg_source_2_pipeline[decode]	<= instruction[24:20];
			reg_destinate_pipeline[decode]	<= instruction[11:7];

			if (instruction[6:0] == op_R_type) begin
				type_pipeline[decode]		<= type_register;
			end else if (instruction[6:0] == op_I_type_load) begin
				type_pipeline[decode]		<= type_load;
			end else if (instruction[6:0] == op_S_type) begin
				type_pipeline[decode]		<= type_store;
			end else if (instruction[6:0] == op_I_type_other || instruction[6:0] == op_I_type_jump) begin
				type_pipeline[decode]		<= type_immediate;
			end else if (instruction[6:0] == op_B_type) begin
				type_pipeline[decode]		<= type_branch;
			end
		end
	end


	// Stage 2: Decode and get the source reg 

	

	always @ (posedge clk) begin
		if (load_stall) begin
			instruction_deconde_2 <= instruction_deconde_2;
			pc_decode_2 <= pc_decode_2;
		end else if (control_hazard_stall) begin
			instruction_deconde_2 <= 32'h00000013;
			pc_decode_2 <= pc_decode_2;
		end else begin
			instruction_deconde_2 <= instruction;
			pc_decode_2 <= pc;
		end

		if (instruction_deconde_2[6:0] == op_R_type) begin
			type_pipeline[execute] <= type_register;
		end else if (instruction_deconde_2[6:0] == op_I_type_load) begin
			type_pipeline[execute] <= type_load;
		end else if (instruction_deconde_2[6:0] == op_S_type) begin
			type_pipeline[execute] <= type_store;
		end else if (instruction_deconde_2[6:0] == op_I_type_other || instruction_deconde_2[6:0] == op_I_type_jump) begin
			type_pipeline[execute] <= type_immediate;
		end else if (instruction_deconde_2[6:0] == op_B_type[6:0]) begin
			type_pipeline[execute] <= type_branch;
		end

		reg_source_1_pipeline[execute] <= instruction_deconde_2[19:15];
		reg_source_2_pipeline[execute] <= instruction_deconde_2[24:20];
		reg_destinate_pipeline[execute] <= instruction_deconde_2[11:7];

		if (load_stall) begin
		 	reg_source_1_pipeline[execute] <= 0;
		 	reg_source_2_pipeline[execute] <= 0;
		 	reg_destinate_pipeline[execute] <= 0;
		 	type_pipeline[execute] <= type_immediate;
		end 
	end


	// Stage 3: Execute

	

	always @ (posedge clk) begin
		if (load_stall) begin
			instruction_execute_3 <= 32'h00000013;
			pc_execute_3 <= pc_execute_3;
		end else begin
			pc_execute_3 <= pc_decode_2;
			instruction_execute_3 <= instruction_deconde_2;
		end

		if (instruction_execute_3[6:0] == op_R_type) begin
			type_pipeline[memory] <= type_register;
		end else if (instruction_execute_3[6:0] == op_I_type_load) begin
			type_pipeline[memory] <= type_load;
		end else if (instruction_execute_3[6:0] == op_S_type) begin
			type_pipeline[memory] <= type_store;
		end else if (instruction_execute_3[6:0] == op_I_type_other || instruction_execute_3[6:0] == op_I_type_jump) begin
			type_pipeline[memory] <= type_immediate;
		end else if (instruction_execute_3[6:0] == op_B_type[6:0]) begin
			type_pipeline[memory] <= type_branch;
		end

		reg_source_1_pipeline[memory] <= instruction_execute_3[19:15];
		reg_source_2_pipeline[memory] <= instruction_execute_3[24:20];
		reg_destinate_pipeline[memory] <= instruction_execute_3[11:7];

	end

	// Stage 4: Memory

	

	always @ (posedge clk) begin
		instruction_memory_4 <= instruction_execute_3;
		pc_memory_4 <= pc_execute_3;

		alu_out_memory_4 <= alu_out;
		ram_write_data <= reg_source_2_data;

		if (instruction_memory_4[6:0] == op_R_type) begin
			type_pipeline[writeback] <= type_register;
		end else if (instruction_memory_4[6:0] == op_I_type_load) begin
			type_pipeline[writeback] <= type_load;
		end else if (instruction_memory_4[6:0] == op_S_type) begin
			type_pipeline[writeback] <= type_store;
		end else if (instruction_memory_4[6:0] == op_I_type_other || instruction_memory_4[6:0] == op_I_type_jump) begin
			type_pipeline[writeback] <= type_immediate;
		end else if (instruction_memory_4[6:0] == op_B_type[6:0]) begin
			type_pipeline[writeback] <= type_branch;
		end

		reg_source_1_pipeline[writeback] <= instruction_memory_4[19:15];
		reg_source_2_pipeline[writeback] <= instruction_memory_4[24:20];
		reg_destinate_pipeline[writeback] <= instruction_memory_4[11:7];

	end

	// Stage 5: writeback

	

	always @ (posedge clk) begin
		instruction_writeback_5 <= instruction_memory_4;
		ram_read_data_writeback_5 <= ram_read_data;

		case (reg_writeback_selection) 
		    1:
		        begin
		            reg_write_data_writeback_5 <= alu_out_memory_4;
		        end
		    2:
		        begin
		            reg_write_data_writeback_5 <= pc_memory_4 + 4;
		        end
		endcase
	end
	
endmodule